// Copyright 2014 PDFium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com

#include "../../include/javascript/JavaScript.h"
#include "../../include/javascript/IJavaScript.h"
#include "../../include/javascript/JS_Define.h"
#include "../../include/javascript/JS_Object.h"
#include "../../include/javascript/JS_Value.h"
#include "../../include/javascript/Document.h"
#include "../../include/javascript/JS_EventHandler.h"
#include "../../include/javascript/JS_Context.h"
#include "../../include/javascript/JS_Runtime.h"
#include "../../include/javascript/app.h"
#include "../../include/javascript/Field.h"
#include "../../include/javascript/Icon.h"
#include "../../include/javascript/resource.h"

#include "../../../third_party/base/numerics/safe_math.h"

static v8::Isolate* GetIsolate(IFXJS_Context* cc)
{
    CJS_Context* pContext = (CJS_Context *)cc;
    ASSERT(pContext != NULL);

    CJS_Runtime* pRuntime = pContext->GetJSRuntime();
    ASSERT(pRuntime != NULL);

    return pRuntime->GetIsolate();
}

BEGIN_JS_STATIC_CONST(CJS_PrintParamsObj)
END_JS_STATIC_CONST()

BEGIN_JS_STATIC_PROP(CJS_PrintParamsObj)
END_JS_STATIC_PROP()

BEGIN_JS_STATIC_METHOD(CJS_PrintParamsObj)
END_JS_STATIC_METHOD()

IMPLEMENT_JS_CLASS(CJS_PrintParamsObj, PrintParamsObj)

PrintParamsObj::PrintParamsObj(CJS_Object* pJSObject)
: CJS_EmbedObj(pJSObject)
{
    bUI = TRUE;
    nStart = 0;
    nEnd = 0;
    bSilent = FALSE;
    bShrinkToFit = FALSE;
    bPrintAsImage = FALSE;
    bReverse = FALSE;
    bAnnotations = TRUE;
}

/* ---------------------- Document ---------------------- */

#define MINWIDTH  5.0f
#define MINHEIGHT 5.0f

BEGIN_JS_STATIC_CONST(CJS_Document)
END_JS_STATIC_CONST()

BEGIN_JS_STATIC_PROP(CJS_Document)
    JS_STATIC_PROP_ENTRY(ADBE)
    JS_STATIC_PROP_ENTRY(author)
    JS_STATIC_PROP_ENTRY(baseURL)
    JS_STATIC_PROP_ENTRY(bookmarkRoot)
    JS_STATIC_PROP_ENTRY(calculate)
    JS_STATIC_PROP_ENTRY(Collab)
    JS_STATIC_PROP_ENTRY(creationDate)
    JS_STATIC_PROP_ENTRY(creator)
    JS_STATIC_PROP_ENTRY(delay)
    JS_STATIC_PROP_ENTRY(dirty)
    JS_STATIC_PROP_ENTRY(documentFileName)
    JS_STATIC_PROP_ENTRY(external)
    JS_STATIC_PROP_ENTRY(filesize)
    JS_STATIC_PROP_ENTRY(icons)
    JS_STATIC_PROP_ENTRY(info)
    JS_STATIC_PROP_ENTRY(keywords)
    JS_STATIC_PROP_ENTRY(layout)
    JS_STATIC_PROP_ENTRY(media)
    JS_STATIC_PROP_ENTRY(modDate)
    JS_STATIC_PROP_ENTRY(mouseX)
    JS_STATIC_PROP_ENTRY(mouseY)
    JS_STATIC_PROP_ENTRY(numFields)
    JS_STATIC_PROP_ENTRY(numPages)
    JS_STATIC_PROP_ENTRY(pageNum)
    JS_STATIC_PROP_ENTRY(pageWindowRect)
    JS_STATIC_PROP_ENTRY(path)
    JS_STATIC_PROP_ENTRY(producer)
    JS_STATIC_PROP_ENTRY(subject)
    JS_STATIC_PROP_ENTRY(title)
    JS_STATIC_PROP_ENTRY(zoom)
    JS_STATIC_PROP_ENTRY(zoomType)
END_JS_STATIC_PROP()

BEGIN_JS_STATIC_METHOD(CJS_Document)
    JS_STATIC_METHOD_ENTRY(addAnnot)
    JS_STATIC_METHOD_ENTRY(addField)
    JS_STATIC_METHOD_ENTRY(addLink)
    JS_STATIC_METHOD_ENTRY(addIcon)
    JS_STATIC_METHOD_ENTRY(calculateNow)
    JS_STATIC_METHOD_ENTRY(closeDoc)
    JS_STATIC_METHOD_ENTRY(createDataObject)
    JS_STATIC_METHOD_ENTRY(deletePages)
    JS_STATIC_METHOD_ENTRY(exportAsText)
    JS_STATIC_METHOD_ENTRY(exportAsFDF)
    JS_STATIC_METHOD_ENTRY(exportAsXFDF)
    JS_STATIC_METHOD_ENTRY(extractPages)
    JS_STATIC_METHOD_ENTRY(getAnnot)
    JS_STATIC_METHOD_ENTRY(getAnnots)
    JS_STATIC_METHOD_ENTRY(getAnnot3D)
    JS_STATIC_METHOD_ENTRY(getAnnots3D)
    JS_STATIC_METHOD_ENTRY(getField)
    JS_STATIC_METHOD_ENTRY(getIcon)
    JS_STATIC_METHOD_ENTRY(getLinks)
    JS_STATIC_METHOD_ENTRY(getNthFieldName)
    JS_STATIC_METHOD_ENTRY(getOCGs)
    JS_STATIC_METHOD_ENTRY(getPageBox)
    JS_STATIC_METHOD_ENTRY(getPageNthWord)
    JS_STATIC_METHOD_ENTRY(getPageNthWordQuads)
    JS_STATIC_METHOD_ENTRY(getPageNumWords)
    JS_STATIC_METHOD_ENTRY(getPrintParams)
    JS_STATIC_METHOD_ENTRY(getURL)
    JS_STATIC_METHOD_ENTRY(importAnFDF)
    JS_STATIC_METHOD_ENTRY(importAnXFDF)
    JS_STATIC_METHOD_ENTRY(importTextData)
    JS_STATIC_METHOD_ENTRY(insertPages)
    JS_STATIC_METHOD_ENTRY(mailForm)
    JS_STATIC_METHOD_ENTRY(print)
    JS_STATIC_METHOD_ENTRY(removeField)
    JS_STATIC_METHOD_ENTRY(replacePages)
    JS_STATIC_METHOD_ENTRY(resetForm)
    JS_STATIC_METHOD_ENTRY(removeIcon)
    JS_STATIC_METHOD_ENTRY(saveAs)
    JS_STATIC_METHOD_ENTRY(submitForm)
    JS_STATIC_METHOD_ENTRY(mailDoc)
END_JS_STATIC_METHOD()

IMPLEMENT_JS_CLASS(CJS_Document, Document)

FX_BOOL CJS_Document::InitInstance(IFXJS_Context* cc)
{
    CJS_Context* pContext = (CJS_Context*)cc;
    ASSERT(pContext != NULL);

    Document* pDoc = (Document*)GetEmbedObject();
    ASSERT(pDoc != NULL);

    pDoc->AttachDoc(pContext->GetReaderDocument());
    pDoc->SetIsolate(pContext->GetJSRuntime()->GetIsolate());
    return TRUE;
};

/* --------------------------------- Document --------------------------------- */

Document::Document(CJS_Object* pJSObject) : CJS_EmbedObj(pJSObject),
    m_isolate(NULL),
    m_pIconTree(NULL),
    m_pDocument(NULL),
    m_cwBaseURL(L""),
    m_bDelay(FALSE)
{
}

Document::~Document()
{
    if (m_pIconTree)
    {
        m_pIconTree->DeleteIconTree();
        delete m_pIconTree;
        m_pIconTree = NULL;
    }
    for (int i=0; i<m_DelayData.GetSize(); i++)
    {
        if (CJS_DelayData* pData = m_DelayData.GetAt(i))
        {
            delete pData;
            pData = NULL;
            m_DelayData.SetAt(i, NULL);

        }
    }

    m_DelayData.RemoveAll();
    m_DelayAnnotData.RemoveAll();
}

//the total number of fileds in document.
FX_BOOL Document::numFields(IFXJS_Context* cc, CJS_PropValue& vp, CFX_WideString& sError)
{
    if (vp.IsSetting()) {
        CJS_Context* pContext = static_cast<CJS_Context*>(cc);
        sError = JSGetStringFromID(pContext, IDS_STRING_JSREADONLY);
        return FALSE;
    }
    CPDFSDK_InterForm *pInterForm = m_pDocument->GetInterForm();
    CPDF_InterForm *pPDFForm = pInterForm->GetInterForm();
    vp << (int)pPDFForm->CountFields();
    return TRUE;
}

FX_BOOL Document::dirty(IFXJS_Context* cc, CJS_PropValue& vp, CFX_WideString& sError)
{
    ASSERT(m_pDocument != NULL);

    if (vp.IsGetting())
    {
        if (m_pDocument->GetChangeMark())
            vp << true;
        else
            vp << false;
    }
    else
    {
        bool bChanged = false;

        vp >> bChanged;

        if (bChanged)
            m_pDocument->SetChangeMark();
        else
            m_pDocument->ClearChangeMark();
    }

    return TRUE;
}

FX_BOOL Document::ADBE(IFXJS_Context* cc, CJS_PropValue& vp, CFX_WideString& sError)
{
    ASSERT(m_pDocument != NULL);

    if (vp.IsGetting())
    {
        vp.SetNull();
    }
    else
    {
    }

    return TRUE;
}

FX_BOOL Document::pageNum(IFXJS_Context* cc, CJS_PropValue& vp, CFX_WideString& sError)
{
    ASSERT(m_pDocument != NULL);

    if (vp.IsGetting())
    {
        if (CPDFSDK_PageView* pPageView = m_pDocument->GetCurrentView())
        {
            vp << pPageView->GetPageIndex();
        }
    }
    else
    {
        int iPageCount = m_pDocument->GetPageCount();
        int iPageNum = 0;
        vp >> iPageNum;

        CPDFDoc_Environment* pEnv = m_pDocument->GetEnv();
        if (iPageNum >= 0 && iPageNum < iPageCount)
        {
             pEnv->JS_docgotoPage(iPageNum);
        }
        else if (iPageNum >= iPageCount)
        {
             pEnv->JS_docgotoPage(iPageCount-1);
        }
        else if (iPageNum < 0)
        {
             pEnv->JS_docgotoPage(0);
        }
    }

    return TRUE;
}

FX_BOOL Document::ParserParams(JSObject* pObj,CJS_AnnotObj& annotobj)
{
    // Not supported.
    return TRUE;
}

FX_BOOL Document::addAnnot(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    // Not supported.
    return TRUE;
}

FX_BOOL Document::addField(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    // Not supported.
    return TRUE;
}

FX_BOOL Document::exportAsText(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    // Unsafe, not supported.
    return TRUE;
}

FX_BOOL Document::exportAsFDF(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    // Unsafe, not supported.
    return TRUE;
}

FX_BOOL Document::exportAsXFDF(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    // Unsafe, not supported.
    return TRUE;
}

//Maps a field object in PDF document to a JavaScript variable
//comment:
//note: the paremter cName, this is clue how to treat if the cName is not a valiable filed name in this document

FX_BOOL Document::getField(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    CJS_Context* pContext = (CJS_Context*)cc;
    if (params.size() < 1) {
        sError = JSGetStringFromID(pContext, IDS_STRING_JSPARAMERROR);
        return FALSE;
    }

    CFX_WideString wideName = params[0].ToCFXWideString();

    CPDFSDK_InterForm* pInterForm = m_pDocument->GetInterForm();
    CPDF_InterForm* pPDFForm = pInterForm->GetInterForm();
    if (pPDFForm->CountFields(wideName) <= 0)
    {
        vRet.SetNull();
        return TRUE;
    }

    CJS_Runtime* pRuntime = pContext->GetJSRuntime();
    JSFXObject pFieldObj = JS_NewFxDynamicObj(*pRuntime, pContext, JS_GetObjDefnID(*pRuntime, L"Field"));

    v8::Isolate* isolate = GetIsolate(cc);
    CJS_Field* pJSField = (CJS_Field*)JS_GetPrivate(isolate,pFieldObj);
    Field* pField = (Field *)pJSField->GetEmbedObject();
    pField->AttachField(this, wideName);

    vRet = pJSField;
    return TRUE;
}

//Gets the name of the nth field in the document
FX_BOOL Document::getNthFieldName(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    CJS_Context* pContext = (CJS_Context*)cc;
    if (params.size() != 1) {
        sError = JSGetStringFromID(pContext, IDS_STRING_JSPARAMERROR);
        return FALSE;
    }

    int nIndex = params[0].ToInt();
    if (nIndex < 0) {
        sError = JSGetStringFromID(pContext, IDS_STRING_JSVALUEERROR);
        return FALSE;
    }

    CPDFSDK_InterForm* pInterForm = m_pDocument->GetInterForm();
    CPDF_InterForm* pPDFForm = pInterForm->GetInterForm();
    CPDF_FormField* pField = pPDFForm->GetField(nIndex);
    if (!pField)
        return FALSE;

    vRet = pField->GetFullName().c_str();
    return TRUE;
}

FX_BOOL Document::importAnFDF(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    // Unsafe, not supported.
    return TRUE;
}

FX_BOOL Document::importAnXFDF(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    // Unsafe, not supported.
    return TRUE;
}

FX_BOOL Document::importTextData(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    // Unsafe, not supported.
    return TRUE;
}

//exports the form data and mails the resulting fdf file as an attachment to all recipients.
//comment: need reader supports
//note:
//int CPDFSDK_Document::mailForm(FX_BOOL bUI,String cto,string ccc,string cbcc,string cSubject,string cms);

FX_BOOL Document::mailForm(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    ASSERT(m_pDocument != NULL);

    if (!m_pDocument->GetPermissions(FPDFPERM_EXTRACT_ACCESS)) return FALSE;

    int iLength = params.size();

    FX_BOOL bUI = iLength > 0 ? params[0].ToBool() : TRUE;
    CFX_WideString cTo = iLength > 1 ? params[1].ToCFXWideString() : L"";
    CFX_WideString cCc = iLength > 2 ? params[2].ToCFXWideString() : L"";
    CFX_WideString cBcc = iLength > 3 ? params[3].ToCFXWideString() : L"";
    CFX_WideString cSubject = iLength > 4 ? params[4].ToCFXWideString() : L"";
    CFX_WideString cMsg = iLength > 5 ? params[5].ToCFXWideString() : L"";

    CPDFSDK_InterForm* pInterForm = (CPDFSDK_InterForm*)m_pDocument->GetInterForm();
    ASSERT(pInterForm != NULL);

    CFX_ByteTextBuf textBuf;
    if (!pInterForm->ExportFormToFDFTextBuf(textBuf))
        return FALSE;

    CJS_Context* pContext = (CJS_Context*)cc;
    ASSERT(pContext != NULL);
    CPDFDoc_Environment* pEnv = pContext->GetReaderApp();
    ASSERT(pEnv != NULL);
    CJS_Runtime* pRuntime = pContext->GetJSRuntime();
    ASSERT(pRuntime != NULL);

    pRuntime->BeginBlock();
    pEnv->JS_docmailForm(textBuf.GetBuffer(), textBuf.GetLength(), bUI, cTo.c_str(), cSubject.c_str(), cCc.c_str(), cBcc.c_str(), cMsg.c_str());
    pRuntime->EndBlock();
    return TRUE;
}

FX_BOOL Document::print(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    CJS_Context* pContext = (CJS_Context*)cc;
    ASSERT(pContext != NULL);
    CJS_Runtime* pRuntime = pContext->GetJSRuntime();
    ASSERT(pRuntime != NULL);

    FX_BOOL bUI = TRUE;
    int nStart = 0;
    int nEnd = 0;
    FX_BOOL bSilent = FALSE;
    FX_BOOL bShrinkToFit = FALSE;
    FX_BOOL bPrintAsImage = FALSE;
    FX_BOOL bReverse = FALSE;
    FX_BOOL bAnnotations = FALSE;

    int nlength = params.size();
    if(nlength ==9)
    {
        if (params[8].GetType() == VT_fxobject)
        {
            JSFXObject pObj = params[8].ToV8Object();
            {
                if (JS_GetObjDefnID(pObj) == JS_GetObjDefnID(*pRuntime, L"PrintParamsObj"))
                {
                    if (CJS_Object* pJSObj = params[8].ToCJSObject())
                    {
                            if (PrintParamsObj* pprintparamsObj = (PrintParamsObj*)pJSObj->GetEmbedObject())
                            {
                                bUI = pprintparamsObj->bUI;
                                nStart = pprintparamsObj->nStart;
                                nEnd = pprintparamsObj->nEnd;
                                bSilent = pprintparamsObj->bSilent;
                                bShrinkToFit = pprintparamsObj->bShrinkToFit;
                                bPrintAsImage = pprintparamsObj->bPrintAsImage;
                                bReverse = pprintparamsObj->bReverse;
                                bAnnotations = pprintparamsObj->bAnnotations;
                            }
                    }
                }
            }
        }
    }
    else
    {
        if(nlength >= 1)
            bUI = params[0].ToBool();
        if(nlength >= 2)
            nStart = params[1].ToInt();
        if(nlength >= 3)
            nEnd = params[2].ToInt();
        if(nlength >= 4)
            bSilent = params[3].ToBool();
        if(nlength >= 5)
            bShrinkToFit = params[4].ToBool();
        if(nlength >= 6)
            bPrintAsImage = params[5].ToBool();
        if(nlength >= 7)
            bReverse = params[6].ToBool();
        if(nlength >= 8)
            bAnnotations = params[7].ToBool();
    }

    ASSERT(m_pDocument != NULL);

    if (CPDFDoc_Environment* pEnv = m_pDocument->GetEnv())
    {
        pEnv->JS_docprint(bUI, nStart, nEnd, bSilent, bShrinkToFit, bPrintAsImage, bReverse, bAnnotations);
        return TRUE;
    }
    return FALSE;
}

//removes the specified field from the document.
//comment:
//note: if the filed name is not retional, adobe is dumb for it.

FX_BOOL Document::removeField(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    ASSERT(m_pDocument != NULL);

    if (!(m_pDocument->GetPermissions(FPDFPERM_MODIFY) ||
        m_pDocument->GetPermissions(FPDFPERM_ANNOT_FORM))) return FALSE;

    CJS_Context* pContext = (CJS_Context*)cc;
    if (params.size() != 1) {
        sError = JSGetStringFromID(pContext, IDS_STRING_JSPARAMERROR);
        return FALSE;
    }

    CFX_WideString sFieldName = params[0].ToCFXWideString();
    CPDFSDK_InterForm* pInterForm = (CPDFSDK_InterForm*)m_pDocument->GetInterForm();
    ASSERT(pInterForm != NULL);

    CFX_PtrArray widgets;
    pInterForm->GetWidgets(sFieldName, widgets);

    int nSize = widgets.GetSize();

    if (nSize > 0)
    {
        for (int i=0; i<nSize; i++)
        {
            CPDFSDK_Widget* pWidget = (CPDFSDK_Widget*)widgets[i];
            ASSERT(pWidget != NULL);

            CPDF_Rect rcAnnot = pWidget->GetRect();
            rcAnnot.left -= 1;
            rcAnnot.bottom -= 1;
            rcAnnot.right += 1;
            rcAnnot.top += 1;

            CFX_RectArray aRefresh;
            aRefresh.Add(rcAnnot);

            CPDF_Page* pPage = pWidget->GetPDFPage();
            ASSERT(pPage != NULL);

            CPDFSDK_PageView* pPageView = m_pDocument->GetPageView(pPage);
            pPageView->DeleteAnnot(pWidget);

            pPageView->UpdateRects(aRefresh);
        }
        m_pDocument->SetChangeMark();
    }

    return TRUE;
}

//reset filed values within a document.
//comment:
//note: if the fields names r not rational, aodbe is dumb for it.

FX_BOOL Document::resetForm(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    ASSERT(m_pDocument != NULL);

    if (!(m_pDocument->GetPermissions(FPDFPERM_MODIFY) ||
        m_pDocument->GetPermissions(FPDFPERM_ANNOT_FORM) ||
        m_pDocument->GetPermissions(FPDFPERM_FILL_FORM))) return FALSE;

    CPDFSDK_InterForm* pInterForm = (CPDFSDK_InterForm*)m_pDocument->GetInterForm();
    ASSERT(pInterForm != NULL);

    CPDF_InterForm* pPDFForm = pInterForm->GetInterForm();
    ASSERT(pPDFForm != NULL);

    v8::Isolate* isolate = GetIsolate(cc);
    CJS_Array aName(isolate);

    if (params.size() > 0)
    {
        switch (params[0].GetType())
        {
        default:
            aName.Attach(params[0].ToV8Array());
            break;
        case VT_string:
            aName.SetElement(0,params[0]);
            break;
        }

        CFX_PtrArray aFields;

        for (int i=0,isz=aName.GetLength(); i<isz; i++)
        {
            CJS_Value valElement(isolate);
            aName.GetElement(i,valElement);
            CFX_WideString swVal = valElement.ToCFXWideString();

            for (int j=0,jsz=pPDFForm->CountFields(swVal); j<jsz; j++)
            {
                aFields.Add((void*)pPDFForm->GetField(j,swVal));
            }
        }

        if (aFields.GetSize() > 0)
        {
            pPDFForm->ResetForm(aFields, TRUE, TRUE);
            m_pDocument->SetChangeMark();

        }
    }
    else
    {
        pPDFForm->ResetForm(TRUE);
        m_pDocument->SetChangeMark();

    }

    return TRUE;
}


FX_BOOL Document::saveAs(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
  // Unsafe, not supported.
  return TRUE;
}


FX_BOOL Document::submitForm(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    ASSERT(m_pDocument != NULL);
    CJS_Context* pContext = (CJS_Context*)cc;
    int nSize = params.size();
    if (nSize < 1) {
        sError = JSGetStringFromID(pContext, IDS_STRING_JSPARAMERROR);
        return FALSE;
    }

    CFX_WideString strURL;
    FX_BOOL bFDF = TRUE;
    FX_BOOL bEmpty = FALSE;
    v8::Isolate* isolate = GetIsolate(cc);
    CJS_Array aFields(isolate);

    CJS_Value v = params[0];
    if (v.GetType() == VT_string)
    {
        strURL = params[0].ToCFXWideString();
        if (nSize > 1)
            bFDF = params[1].ToBool();
        if (nSize > 2)
            bEmpty = params[2].ToBool();
        if (nSize > 3)
            aFields.Attach(params[3].ToV8Array());
    }
    else if (v.GetType() == VT_object)
    {
        JSObject pObj = params[0].ToV8Object();
        v8::Local<v8::Value> pValue = JS_GetObjectElement(isolate, pObj, L"cURL");
        if (!pValue.IsEmpty())
            strURL = CJS_Value(isolate, pValue, GET_VALUE_TYPE(pValue)).ToCFXWideString();
        pValue = JS_GetObjectElement(isolate, pObj, L"bFDF");
        bFDF = CJS_Value(isolate, pValue, GET_VALUE_TYPE(pValue)).ToBool();
        pValue = JS_GetObjectElement(isolate, pObj, L"bEmpty");
        bEmpty = CJS_Value(isolate, pValue, GET_VALUE_TYPE(pValue)).ToBool();
        pValue = JS_GetObjectElement(isolate, pObj,L"aFields");
        aFields.Attach(CJS_Value(isolate, pValue, GET_VALUE_TYPE(pValue)).ToV8Array());
    }

    CJS_Runtime* pRuntime = pContext->GetJSRuntime();
    CPDFSDK_InterForm* pInterForm = (CPDFSDK_InterForm*)m_pDocument->GetInterForm();
    CPDF_InterForm* pPDFInterForm = pInterForm->GetInterForm();
    FX_BOOL bAll = (aFields.GetLength() == 0);
    if (bAll && bEmpty)
    {
        if (pPDFInterForm->CheckRequiredFields())
        {
            pRuntime->BeginBlock();
            pInterForm->SubmitForm(strURL, FALSE);
            pRuntime->EndBlock();
        }
        return TRUE;
    }

    CFX_PtrArray fieldObjects;
    for (int i=0,sz=aFields.GetLength(); i<sz; i++)
    {
        CJS_Value valName(isolate);
        aFields.GetElement(i, valName);

        CFX_WideString sName = valName.ToCFXWideString();
        CPDF_InterForm* pPDFForm = pInterForm->GetInterForm();
        for (int j = 0, jsz = pPDFForm->CountFields(sName); j < jsz; ++j)
        {
            CPDF_FormField* pField = pPDFForm->GetField(j, sName);
            if (!bEmpty && pField->GetValue().IsEmpty())
                continue;

            fieldObjects.Add(pField);
        }
    }

    if (pPDFInterForm->CheckRequiredFields(&fieldObjects, TRUE))
    {
        pRuntime->BeginBlock();
        pInterForm->SubmitFields(strURL, fieldObjects, TRUE, !bFDF);
        pRuntime->EndBlock();
    }
    return TRUE;
}

//////////////////////////////////////////////////////////////////////////////////////////////

void Document::AttachDoc(CPDFSDK_Document *pDoc)
{
    m_pDocument = pDoc;
}

CPDFSDK_Document * Document::GetReaderDoc()
{
    return m_pDocument;
}

FX_BOOL Document::ExtractFileName(CPDFSDK_Document *pDoc,CFX_ByteString &strFileName)
{
    return FALSE;
}

FX_BOOL Document::ExtractFolderName(CPDFSDK_Document *pDoc,CFX_ByteString &strFolderName)
{
    return FALSE;
}

FX_BOOL Document::bookmarkRoot(IFXJS_Context* cc, CJS_PropValue& vp, CFX_WideString& sError)
{
    return TRUE;
}

FX_BOOL Document::mailDoc(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    ASSERT(m_pDocument != NULL);

    FX_BOOL bUI = TRUE;
    CFX_WideString cTo = L"";
    CFX_WideString cCc = L"";
    CFX_WideString cBcc = L"";
    CFX_WideString cSubject = L"";
    CFX_WideString cMsg = L"";

    if (params.size() >= 1)
        bUI = params[0].ToBool();
    if (params.size() >= 2)
        cTo = params[1].ToCFXWideString();
    if (params.size() >= 3)
        cCc = params[2].ToCFXWideString();
    if (params.size() >= 4)
        cBcc = params[3].ToCFXWideString();
    if (params.size() >= 5)
        cSubject = params[4].ToCFXWideString();
    if (params.size() >= 6)
        cMsg = params[5].ToCFXWideString();

    v8::Isolate* isolate = GetIsolate(cc);

    if(params.size() >= 1 && params[0].GetType() == VT_object)
    {
        JSObject pObj = params[0].ToV8Object();

        v8::Local<v8::Value> pValue = JS_GetObjectElement(isolate,pObj, L"bUI");
        bUI = CJS_Value(isolate,pValue,GET_VALUE_TYPE(pValue)).ToInt();

        pValue = JS_GetObjectElement(isolate,pObj, L"cTo");
        cTo = CJS_Value(isolate,pValue,GET_VALUE_TYPE(pValue)).ToCFXWideString();

        pValue = JS_GetObjectElement(isolate,pObj, L"cCc");
        cCc = CJS_Value(isolate,pValue,GET_VALUE_TYPE(pValue)).ToCFXWideString();

        pValue = JS_GetObjectElement(isolate,pObj, L"cBcc");
        cBcc = CJS_Value(isolate,pValue,GET_VALUE_TYPE(pValue)).ToCFXWideString();

        pValue = JS_GetObjectElement(isolate,pObj, L"cSubject");
        cSubject = CJS_Value(isolate,pValue,GET_VALUE_TYPE(pValue)).ToCFXWideString();

        pValue = JS_GetObjectElement(isolate,pObj, L"cMsg");
        cMsg = CJS_Value(isolate,pValue,GET_VALUE_TYPE(pValue)).ToCFXWideString();

    }

    CJS_Context* pContext = (CJS_Context*)cc;
    ASSERT(pContext != NULL);
    CJS_Runtime* pRuntime = pContext->GetJSRuntime();
    ASSERT(pRuntime != NULL);

    pRuntime->BeginBlock();
    CPDFDoc_Environment* pEnv = pRuntime->GetReaderApp();
    pEnv->JS_docmailForm(NULL, 0, bUI, cTo.c_str(), cSubject.c_str(), cCc.c_str(), cBcc.c_str(), cMsg.c_str());
    pRuntime->EndBlock();

    return TRUE;
}

FX_BOOL Document::author(IFXJS_Context* cc, CJS_PropValue& vp, CFX_WideString& sError)
{
    ASSERT(m_pDocument != NULL);

    CPDF_Dictionary* pDictionary = m_pDocument->GetDocument()->GetInfo();
    if (!pDictionary)return FALSE;

    if (vp.IsGetting())
    {
        vp << pDictionary->GetUnicodeText("Author");
        return TRUE;
    }
    else
    {
        if (!m_pDocument->GetPermissions(FPDFPERM_MODIFY)) return FALSE;

        CFX_WideString csAuthor;
        vp >> csAuthor;
        pDictionary->SetAtString("Author", PDF_EncodeText(csAuthor));
        m_pDocument->SetChangeMark();
        return TRUE;
    }
}

FX_BOOL Document::info(IFXJS_Context* cc, CJS_PropValue& vp, CFX_WideString& sError)
{
    ASSERT(m_pDocument != NULL);

    CPDF_Dictionary* pDictionary = m_pDocument->GetDocument()->GetInfo();
    if (!pDictionary)
        return FALSE;

    CFX_WideString cwAuthor         = pDictionary->GetUnicodeText("Author");
    CFX_WideString cwTitle          = pDictionary->GetUnicodeText("Title");
    CFX_WideString cwSubject        = pDictionary->GetUnicodeText("Subject");
    CFX_WideString cwKeywords       = pDictionary->GetUnicodeText("Keywords");
    CFX_WideString cwCreator        = pDictionary->GetUnicodeText("Creator");
    CFX_WideString cwProducer       = pDictionary->GetUnicodeText("Producer");
    CFX_WideString cwCreationDate   = pDictionary->GetUnicodeText("CreationDate");
    CFX_WideString cwModDate        = pDictionary->GetUnicodeText("ModDate");
    CFX_WideString cwTrapped        = pDictionary->GetUnicodeText("Trapped");

    v8::Isolate* isolate = GetIsolate(cc);
    if (vp.IsGetting())
    {
        CJS_Context* pContext = (CJS_Context *)cc;
        CJS_Runtime* pRuntime = pContext->GetJSRuntime();
        JSFXObject  pObj = JS_NewFxDynamicObj(*pRuntime, pContext, -1);
        JS_PutObjectString(isolate, pObj, L"Author", cwAuthor.c_str());
        JS_PutObjectString(isolate, pObj, L"Title", cwTitle.c_str());
        JS_PutObjectString(isolate, pObj, L"Subject", cwSubject.c_str());
        JS_PutObjectString(isolate, pObj, L"Keywords", cwKeywords.c_str());
        JS_PutObjectString(isolate, pObj, L"Creator", cwCreator.c_str());
        JS_PutObjectString(isolate, pObj, L"Producer", cwProducer.c_str());
        JS_PutObjectString(isolate, pObj, L"CreationDate", cwCreationDate.c_str());
        JS_PutObjectString(isolate, pObj, L"ModDate", cwModDate.c_str());
        JS_PutObjectString(isolate, pObj, L"Trapped", cwTrapped.c_str());

        // It's to be compatible to non-standard info dictionary.
        FX_POSITION pos = pDictionary->GetStartPos();
        while(pos)
        {
            CFX_ByteString bsKey;
            CPDF_Object* pValueObj = pDictionary->GetNextElement(pos, bsKey);
            CFX_WideString wsKey  = CFX_WideString::FromUTF8(bsKey, bsKey.GetLength());
            if((pValueObj->GetType()==PDFOBJ_STRING) || (pValueObj->GetType()==PDFOBJ_NAME) )
                JS_PutObjectString(isolate, pObj, wsKey.c_str(), pValueObj->GetUnicodeText().c_str());
            if(pValueObj->GetType()==PDFOBJ_NUMBER)
                JS_PutObjectNumber(isolate,pObj, wsKey.c_str(), (float)pValueObj->GetNumber());
            if(pValueObj->GetType()==PDFOBJ_BOOLEAN)
                JS_PutObjectBoolean(isolate,pObj, wsKey.c_str(), (bool)pValueObj->GetInteger());
        }
        vp << pObj;
    }
    return TRUE;
}

FX_BOOL Document::creationDate(IFXJS_Context* cc, CJS_PropValue& vp, CFX_WideString& sError)
{
    CPDF_Dictionary* pDictionary = m_pDocument->GetDocument()->GetInfo();
    if (!pDictionary)
        return FALSE;

    if (vp.IsGetting())
    {
        vp << pDictionary->GetUnicodeText("CreationDate");
    }
    else
    {
        if (!m_pDocument->GetPermissions(FPDFPERM_MODIFY))
            return FALSE;

        CFX_WideString csCreationDate;
        vp >> csCreationDate;
        pDictionary->SetAtString("CreationDate", PDF_EncodeText(csCreationDate));
        m_pDocument->SetChangeMark();
    }
    return TRUE;
}

FX_BOOL Document::creator(IFXJS_Context* cc, CJS_PropValue& vp, CFX_WideString& sError)
{
    CPDF_Dictionary* pDictionary = m_pDocument->GetDocument()->GetInfo();
    if (!pDictionary)
        return FALSE;

    if (vp.IsGetting())
    {
        vp << pDictionary->GetUnicodeText("Creator");
    }
    else
    {
        if (!m_pDocument->GetPermissions(FPDFPERM_MODIFY))
            return FALSE;

        CFX_WideString csCreator;
        vp >> csCreator;
        pDictionary->SetAtString("Creator", PDF_EncodeText(csCreator));
        m_pDocument->SetChangeMark();
    }
    return TRUE;
}

FX_BOOL Document::delay(IFXJS_Context* cc, CJS_PropValue& vp, CFX_WideString& sError)
{
    if (vp.IsGetting())
    {
        vp << m_bDelay;
    }
    else
    {
        if (!m_pDocument->GetPermissions(FPDFPERM_MODIFY))
            return FALSE;

        vp >> m_bDelay;
        if (m_bDelay)
        {
            for (int i=0,sz=m_DelayData.GetSize(); i<sz; i++)
                delete m_DelayData.GetAt(i);

            m_DelayData.RemoveAll();
        }
        else
        {
            CFX_ArrayTemplate<CJS_DelayData*> DelayDataToProcess;
            for (int i=0,sz=m_DelayData.GetSize(); i < sz; i++)
            {
                if (CJS_DelayData* pData = m_DelayData.GetAt(i))
                {
                    DelayDataToProcess.Add(pData);
                    m_DelayData.SetAt(i, NULL);
                }
            }
            m_DelayData.RemoveAll();
            for (int i=0,sz=DelayDataToProcess.GetSize(); i < sz; i++)
            {
                CJS_DelayData* pData = DelayDataToProcess.GetAt(i);
                Field::DoDelay(m_pDocument, pData);
                DelayDataToProcess.SetAt(i,NULL);
                delete pData;
            }
        }
    }
    return TRUE;
}

FX_BOOL Document::keywords(IFXJS_Context* cc, CJS_PropValue& vp, CFX_WideString& sError)
{
    CPDF_Dictionary* pDictionary = m_pDocument->GetDocument()->GetInfo();
    if (!pDictionary)
        return FALSE;

    if (vp.IsGetting())
    {
        vp << pDictionary->GetUnicodeText("Keywords");
    }
    else
    {
        if (!m_pDocument->GetPermissions(FPDFPERM_MODIFY))
            return FALSE;

        CFX_WideString csKeywords;
        vp >> csKeywords;
        pDictionary->SetAtString("Keywords", PDF_EncodeText(csKeywords));
        m_pDocument->SetChangeMark();
    }
    return TRUE;
}

FX_BOOL Document::modDate(IFXJS_Context* cc, CJS_PropValue& vp, CFX_WideString& sError)
{
    CPDF_Dictionary* pDictionary = m_pDocument->GetDocument()->GetInfo();
    if (!pDictionary)
        return FALSE;

    if (vp.IsGetting())
    {
        vp << pDictionary->GetUnicodeText("ModDate");
    }
    else
    {
        if (!m_pDocument->GetPermissions(FPDFPERM_MODIFY))
            return FALSE;

        CFX_WideString csmodDate;
        vp >> csmodDate;
        pDictionary->SetAtString("ModDate", PDF_EncodeText(csmodDate));
        m_pDocument->SetChangeMark();
    }
    return TRUE;
}

FX_BOOL Document::producer(IFXJS_Context* cc, CJS_PropValue& vp, CFX_WideString& sError)
{
    CPDF_Dictionary* pDictionary = m_pDocument->GetDocument()->GetInfo();
    if (!pDictionary)
        return FALSE;

    if (vp.IsGetting())
    {
        vp << pDictionary->GetUnicodeText("Producer");
    }
    else
    {
        if (!m_pDocument->GetPermissions(FPDFPERM_MODIFY))
            return FALSE;

        CFX_WideString csproducer;
        vp >> csproducer;
        pDictionary->SetAtString("Producer", PDF_EncodeText(csproducer));
        m_pDocument->SetChangeMark();
    }
    return TRUE;
}

FX_BOOL Document::subject(IFXJS_Context* cc, CJS_PropValue& vp, CFX_WideString& sError)
{
    CPDF_Dictionary* pDictionary = m_pDocument->GetDocument()->GetInfo();
    if (!pDictionary)
        return FALSE;

    if (vp.IsGetting())
    {
        vp << pDictionary->GetUnicodeText("Subject");
    }
    else
    {
        if (!m_pDocument->GetPermissions(FPDFPERM_MODIFY))
            return FALSE;

        CFX_WideString cssubject;
        vp >> cssubject;
        pDictionary->SetAtString("Subject", PDF_EncodeText(cssubject));
        m_pDocument->SetChangeMark();
    }
    return TRUE;
}

FX_BOOL Document::title(IFXJS_Context* cc, CJS_PropValue& vp, CFX_WideString& sError)
{
    if (m_pDocument == NULL || m_pDocument->GetDocument() == NULL)
        return FALSE;

    CPDF_Dictionary* pDictionary = m_pDocument->GetDocument()->GetInfo();
    if (!pDictionary)
        return FALSE;

    if (vp.IsGetting())
    {
        vp << pDictionary->GetUnicodeText("Title");
    }
    else
    {
        if (!m_pDocument->GetPermissions(FPDFPERM_MODIFY))
            return FALSE;

        CFX_WideString cstitle;
        vp >> cstitle;
        pDictionary->SetAtString("Title", PDF_EncodeText(cstitle));
        m_pDocument->SetChangeMark();
    }
    return TRUE;
}

FX_BOOL Document::numPages(IFXJS_Context* cc, CJS_PropValue& vp, CFX_WideString& sError)
{
    if (vp.IsSetting()) {
        CJS_Context* pContext = static_cast<CJS_Context*>(cc);
        sError = JSGetStringFromID(pContext, IDS_STRING_JSREADONLY);
        return FALSE;
    }
    vp << m_pDocument->GetPageCount();
    return TRUE;
}

FX_BOOL Document::external(IFXJS_Context* cc, CJS_PropValue& vp, CFX_WideString& sError)
{
    //In Chrome case,should always return true.
    if (vp.IsGetting()) {
        vp << true;
    }
    return TRUE;
}

FX_BOOL Document::filesize(IFXJS_Context* cc, CJS_PropValue& vp, CFX_WideString& sError)
{
    if (vp.IsSetting()) {
        CJS_Context* pContext = static_cast<CJS_Context*>(cc);
        sError = JSGetStringFromID(pContext, IDS_STRING_JSREADONLY);
        return FALSE;
    }
    vp << 0;
    return TRUE;
}

FX_BOOL Document::mouseX(IFXJS_Context* cc, CJS_PropValue& vp, CFX_WideString& sError)
{
    return TRUE;
}

FX_BOOL Document::mouseY(IFXJS_Context* cc, CJS_PropValue& vp, CFX_WideString& sError)
{
    return TRUE;
}

FX_BOOL Document::baseURL(IFXJS_Context* cc, CJS_PropValue& vp, CFX_WideString& sError)
{
    if (vp.IsGetting())
    {
        vp << m_cwBaseURL;
    }
    else
    {
        vp >> m_cwBaseURL;
    }
    return TRUE;
}

FX_BOOL Document::calculate(IFXJS_Context* cc, CJS_PropValue& vp, CFX_WideString& sError)
{
    ASSERT(m_pDocument != NULL);

    CPDFSDK_InterForm* pInterForm = (CPDFSDK_InterForm*)m_pDocument->GetInterForm();
    ASSERT(pInterForm != NULL);

    if (vp.IsGetting())
    {
        if (pInterForm->IsCalculateEnabled())
            vp << true;
        else
            vp << false;
    }
    else
    {
        bool bCalculate;
        vp >> bCalculate;

        pInterForm->EnableCalculate(bCalculate);
    }

    return TRUE;
}

FX_BOOL Document::documentFileName(IFXJS_Context* cc, CJS_PropValue& vp, CFX_WideString& sError)
{
    if (vp.IsSetting()) {
        CJS_Context* pContext = static_cast<CJS_Context*>(cc);
        sError = JSGetStringFromID(pContext, IDS_STRING_JSREADONLY);
        return FALSE;
    }
    CFX_WideString wsFilePath = m_pDocument->GetPath();
    int32_t i = wsFilePath.GetLength() - 1;
    for ( ; i >= 0; i-- )
    {
        if ( wsFilePath.GetAt( i ) == L'\\' || wsFilePath.GetAt( i ) == L'/' )
            break;
    }
    if ( i >= 0 && i < wsFilePath.GetLength() - 1 )
    {
        vp << ( wsFilePath.GetBuffer( wsFilePath.GetLength() ) + i + 1 );
    }else{
        vp << L"";
    }
    return TRUE;
}

CFX_WideString Document::ReversalStr(CFX_WideString cbFrom)
{
    size_t iLength = cbFrom.GetLength();
        pdfium::base::CheckedNumeric<size_t> iSize = sizeof(wchar_t);
    iSize *= (iLength + 1);
    wchar_t* pResult = (wchar_t*)malloc(iSize.ValueOrDie());
    wchar_t* pFrom = (wchar_t*)cbFrom.GetBuffer(iLength);

    for (size_t i = 0; i < iLength; i++)
    {
        pResult[i] = *(pFrom + iLength - i - 1);
    }
    pResult[iLength] = L'\0';

    cbFrom.ReleaseBuffer();
    CFX_WideString cbRet = CFX_WideString(pResult);
    free(pResult);
    pResult = NULL;
    return cbRet;
}

CFX_WideString Document::CutString(CFX_WideString cbFrom)
{
    size_t iLength = cbFrom.GetLength();
    pdfium::base::CheckedNumeric<size_t> iSize = sizeof(wchar_t);
    iSize *= (iLength + 1);
    wchar_t* pResult = (wchar_t*)malloc(iSize.ValueOrDie());
    wchar_t* pFrom = (wchar_t*)cbFrom.GetBuffer(iLength);

    for (int i = 0; i < iLength; i++)
    {
        if (pFrom[i] == L'\\' || pFrom[i] == L'/')
        {
            pResult[i] = L'\0';
            break;
        }
        pResult[i] = pFrom[i];
    }
    pResult[iLength] = L'\0';

    cbFrom.ReleaseBuffer();
    CFX_WideString cbRet = CFX_WideString(pResult);
    free(pResult);
    pResult = NULL;
    return cbRet;
}

FX_BOOL Document::path(IFXJS_Context* cc, CJS_PropValue& vp, CFX_WideString& sError)
{
    if (vp.IsSetting()) {
        CJS_Context* pContext = static_cast<CJS_Context*>(cc);
        sError = JSGetStringFromID(pContext, IDS_STRING_JSREADONLY);
        return FALSE;
    }
    vp << app::SysPathToPDFPath(m_pDocument->GetPath());
    return TRUE;
}

FX_BOOL Document::pageWindowRect(IFXJS_Context* cc, CJS_PropValue& vp, CFX_WideString& sError)
{
    return TRUE;
}

FX_BOOL Document::layout(IFXJS_Context* cc, CJS_PropValue& vp, CFX_WideString& sError)
{
    return TRUE;
}

FX_BOOL Document::addLink(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    return TRUE;
}

FX_BOOL Document::closeDoc(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    ASSERT(m_pDocument != NULL);
    return TRUE;
}

FX_BOOL Document::getPageBox(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    return TRUE;
}

FX_BOOL Document::getAnnot(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    return TRUE;
}

FX_BOOL Document::getAnnots(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    vRet.SetNull();
    return TRUE;
}

FX_BOOL Document::getAnnot3D(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    vRet.SetNull();
    return TRUE;
}

FX_BOOL Document::getAnnots3D(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    vRet = VT_undefined;
    return TRUE;
}

FX_BOOL Document::getOCGs(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    return TRUE;
}

FX_BOOL Document::getLinks(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    return TRUE;
}

bool Document::IsEnclosedInRect(CFX_FloatRect rect, CFX_FloatRect LinkRect)
{
    return (rect.left <= LinkRect.left &&
            rect.top <= LinkRect.top &&
            rect.right >= LinkRect.right &&
            rect.bottom >= LinkRect.bottom);
}

void IconTree::InsertIconElement(IconElement* pNewIcon)
{
    if (!pNewIcon)return;

    if (m_pHead == NULL && m_pEnd == NULL)
    {
        m_pHead = m_pEnd = pNewIcon;
        m_iLength++;
    }
    else
    {
        m_pEnd->NextIcon = pNewIcon;
        m_pEnd = pNewIcon;
        m_iLength++;
    }
}

void IconTree::DeleteIconTree()
{
    if (!m_pHead || !m_pEnd)return;

    IconElement* pTemp = NULL;
    while(m_pEnd != m_pHead)
    {
        pTemp = m_pHead;
        m_pHead = m_pHead->NextIcon;
        delete pTemp;
    }

    delete m_pEnd;
    m_pHead = NULL;
    m_pEnd = NULL;
}

int IconTree::GetLength()
{
    return m_iLength;
}

IconElement* IconTree::operator [](int iIndex)
{
    if (iIndex >= 0 && iIndex <= m_iLength)
    {
        IconElement* pTemp = m_pHead;
        for (int i = 0; i < iIndex; i++)
        {
            pTemp = pTemp->NextIcon;
        }
        return pTemp;
    }
    return NULL;
}

FX_BOOL Document::addIcon(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    CJS_Context* pContext = (CJS_Context*)cc;
    if (params.size() != 2) {
        sError = JSGetStringFromID(pContext, IDS_STRING_JSPARAMERROR);
        return FALSE;
    }

    CFX_WideString swIconName = params[0].ToCFXWideString();
    JSFXObject pJSIcon = params[1].ToV8Object();

    CJS_Runtime* pRuntime = pContext->GetJSRuntime();
    if (JS_GetObjDefnID(pJSIcon) != JS_GetObjDefnID(*pRuntime, L"Icon")) {
        sError = JSGetStringFromID(pContext, IDS_STRING_JSTYPEERROR);
        return FALSE;
    }

    CJS_EmbedObj* pEmbedObj = params[1].ToCJSObject()->GetEmbedObject();
    if (!pEmbedObj) {
        sError = JSGetStringFromID(pContext, IDS_STRING_JSTYPEERROR);
        return FALSE;
    }

    Icon* pIcon = (Icon*)pEmbedObj;
    if (!m_pIconTree)
        m_pIconTree = new IconTree();

    IconElement* pNewIcon = new IconElement();
    pNewIcon->IconName = swIconName;
    pNewIcon->NextIcon = NULL;
    pNewIcon->IconStream = pIcon;
    m_pIconTree->InsertIconElement(pNewIcon);
    return TRUE;
}

FX_BOOL Document::icons(IFXJS_Context* cc, CJS_PropValue& vp, CFX_WideString& sError)
{
    if (vp.IsSetting()) {
        CJS_Context* pContext = static_cast<CJS_Context*>(cc);
        sError = JSGetStringFromID(pContext, IDS_STRING_JSREADONLY);
        return FALSE;
    }

    if (!m_pIconTree)
    {
        vp.SetNull();
        return TRUE;
    }

    CJS_Array Icons(m_isolate);
    IconElement* pIconElement = NULL;
    int iIconTreeLength = m_pIconTree->GetLength();

    CJS_Context* pContext = (CJS_Context *)cc;
    CJS_Runtime* pRuntime = pContext->GetJSRuntime();

    for (int i = 0; i < iIconTreeLength; i++)
    {
        pIconElement = (*m_pIconTree)[i];

        JSFXObject  pObj = JS_NewFxDynamicObj(*pRuntime, pContext, JS_GetObjDefnID(*pRuntime, L"Icon"));
        if (pObj.IsEmpty()) return FALSE;

        CJS_Icon * pJS_Icon = (CJS_Icon *)JS_GetPrivate(pObj);
        if (!pJS_Icon) return FALSE;

        Icon* pIcon = (Icon*)pJS_Icon->GetEmbedObject();
        if (!pIcon)return FALSE;

        pIcon->SetStream(pIconElement->IconStream->GetStream());
        pIcon->SetIconName(pIconElement->IconName);
        Icons.SetElement(i, CJS_Value(m_isolate,pJS_Icon));
    }

    vp << Icons;
    return TRUE;
}

FX_BOOL Document::getIcon(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    CJS_Context* pContext = (CJS_Context *)cc;
    if (params.size() != 1) {
        sError = JSGetStringFromID(pContext, IDS_STRING_JSPARAMERROR);
        return FALSE;
    }

    if(!m_pIconTree)
        return FALSE;
    CFX_WideString swIconName = params[0].ToCFXWideString();
    int iIconCounts = m_pIconTree->GetLength();

    CJS_Runtime* pRuntime = pContext->GetJSRuntime();

    for (int i = 0; i < iIconCounts; i++)
    {
        if ((*m_pIconTree)[i]->IconName == swIconName)
        {
            Icon* pRetIcon = (*m_pIconTree)[i]->IconStream;

            JSFXObject  pObj = JS_NewFxDynamicObj(*pRuntime, pContext, JS_GetObjDefnID(*pRuntime, L"Icon"));
            if (pObj.IsEmpty()) return FALSE;

            CJS_Icon * pJS_Icon = (CJS_Icon *)JS_GetPrivate(pObj);
            if (!pJS_Icon) return FALSE;

            Icon* pIcon = (Icon*)pJS_Icon->GetEmbedObject();
            if (!pIcon)return FALSE;

            pIcon->SetIconName(swIconName);
            pIcon->SetStream(pRetIcon->GetStream());
            vRet = pJS_Icon;
            return TRUE;
        }
    }

    return FALSE;
}

FX_BOOL Document::removeIcon(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
  // Unsafe, no supported.
  return TRUE;
}

FX_BOOL Document::createDataObject(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
  // Unsafe, not implemented.
  return TRUE;
}

FX_BOOL Document::media(IFXJS_Context* cc, CJS_PropValue& vp, CFX_WideString& sError)
{
    return TRUE;
}

FX_BOOL Document::calculateNow(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    ASSERT(m_pDocument != NULL);

    if (!(m_pDocument->GetPermissions(FPDFPERM_MODIFY) ||
        m_pDocument->GetPermissions(FPDFPERM_ANNOT_FORM) ||
        m_pDocument->GetPermissions(FPDFPERM_FILL_FORM))) return FALSE;

    CPDFSDK_InterForm* pInterForm = (CPDFSDK_InterForm*)m_pDocument->GetInterForm();
    ASSERT(pInterForm != NULL);
    pInterForm->OnCalculate();
    return TRUE;
}

FX_BOOL Document::Collab(IFXJS_Context* cc, CJS_PropValue& vp, CFX_WideString& sError)
{
    return TRUE;
}

FX_BOOL Document::getPageNthWord(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    ASSERT(m_pDocument != NULL);

    if (!m_pDocument->GetPermissions(FPDFPERM_EXTRACT_ACCESS)) return FALSE;

    int nPageNo = params.GetSize() > 0 ? params[0].ToInt() : 0;
    int nWordNo = params.GetSize() > 1 ? params[1].ToInt() : 0;
    bool bStrip = params.GetSize() > 2 ? params[2].ToBool() : true;

    CPDF_Document* pDocument = m_pDocument->GetDocument();
    if (!pDocument) return FALSE;

    CJS_Context* pContext = static_cast<CJS_Context*>(cc);
    if (nPageNo < 0 || nPageNo >= pDocument->GetPageCount())
    {
        sError = JSGetStringFromID(pContext, IDS_STRING_JSVALUEERROR);
        return FALSE;
    }

    CPDF_Dictionary* pPageDict = pDocument->GetPage(nPageNo);
    if (!pPageDict) return FALSE;

    CPDF_Page page;
    page.Load(pDocument, pPageDict);
    page.StartParse();
    page.ParseContent();

    FX_POSITION pos = page.GetFirstObjectPosition();

    int nWords = 0;

    CFX_WideString swRet;

    while (pos)
    {
        if (CPDF_PageObject* pPageObj = page.GetNextObject(pos))
        {
            if (pPageObj->m_Type == PDFPAGE_TEXT)
            {
                int nObjWords = CountWords((CPDF_TextObject*)pPageObj);

                if (nWords + nObjWords >= nWordNo)
                {
                    swRet = GetObjWordStr((CPDF_TextObject*)pPageObj, nWordNo - nWords);
                    break;
                }

                nWords += nObjWords;
            }
        }
    }

    if (bStrip)
    {
        swRet.TrimLeft();
        swRet.TrimRight();
    }

    vRet = swRet.c_str();
    return TRUE;
}

FX_BOOL Document::getPageNthWordQuads(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    ASSERT(m_pDocument != NULL);

    if (!m_pDocument->GetPermissions(FPDFPERM_EXTRACT_ACCESS)) return FALSE;

    return FALSE;
}

FX_BOOL Document::getPageNumWords(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    ASSERT(m_pDocument != NULL);

    if (!m_pDocument->GetPermissions(FPDFPERM_EXTRACT_ACCESS)) return FALSE;

    int nPageNo = params.GetSize() > 0 ? params[0].ToInt() : 0;

    CPDF_Document* pDocument = m_pDocument->GetDocument();
    ASSERT(pDocument != NULL);

    CJS_Context* pContext = static_cast<CJS_Context*>(cc);
    if (nPageNo < 0 || nPageNo >= pDocument->GetPageCount())
    {
        sError = JSGetStringFromID(pContext, IDS_STRING_JSVALUEERROR);
        return FALSE;
    }

    CPDF_Dictionary* pPageDict = pDocument->GetPage(nPageNo);
    if (!pPageDict) return FALSE;

    CPDF_Page page;
    page.Load(pDocument, pPageDict);
    page.StartParse();
    page.ParseContent();

    FX_POSITION pos = page.GetFirstObjectPosition();

    int nWords = 0;

    while (pos)
    {
        if (CPDF_PageObject* pPageObj = page.GetNextObject(pos))
        {
            if (pPageObj->m_Type == PDFPAGE_TEXT)
            {
                CPDF_TextObject* pTextObj = (CPDF_TextObject*)pPageObj;
                nWords += CountWords(pTextObj);
            }
        }
    }

    vRet = nWords;

    return TRUE;
}

FX_BOOL Document::getPrintParams(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
    CJS_Context* pContext = (CJS_Context*)cc;
    CJS_Runtime* pRuntime = pContext->GetJSRuntime();
    JSFXObject pRetObj = JS_NewFxDynamicObj(*pRuntime, pContext, JS_GetObjDefnID(*pRuntime, L"PrintParamsObj"));

    // Not implemented yet.

    vRet = pRetObj;
    return TRUE;
}

#define ISLATINWORD(u)  (u != 0x20 && u <= 0x28FF)

int Document::CountWords(CPDF_TextObject* pTextObj)
{
    if (!pTextObj) return 0;

    int nWords = 0;

    CPDF_Font* pFont = pTextObj->GetFont();
    if (!pFont) return 0;

    FX_BOOL bIsLatin = FALSE;

    for (int i=0, sz=pTextObj->CountChars(); i<sz; i++)
    {
        FX_DWORD charcode = -1;
        FX_FLOAT kerning;

        pTextObj->GetCharInfo(i, charcode, kerning);
        CFX_WideString swUnicode = pFont->UnicodeFromCharCode(charcode);

        FX_WORD unicode = 0;
        if (swUnicode.GetLength() > 0)
            unicode = swUnicode[0];

        if (ISLATINWORD(unicode) && bIsLatin)
            continue;

        bIsLatin = ISLATINWORD(unicode);
        if (unicode != 0x20)
            nWords++;
    }

    return nWords;
}

CFX_WideString Document::GetObjWordStr(CPDF_TextObject* pTextObj, int nWordIndex)
{
    ASSERT(pTextObj != NULL);

    CFX_WideString swRet;

    CPDF_Font* pFont = pTextObj->GetFont();
    if (!pFont) return L"";

    int nWords = 0;
    FX_BOOL bIsLatin = FALSE;

    for (int i=0, sz=pTextObj->CountChars(); i<sz; i++)
    {
        FX_DWORD charcode = -1;
        FX_FLOAT kerning;

        pTextObj->GetCharInfo(i, charcode, kerning);
        CFX_WideString swUnicode = pFont->UnicodeFromCharCode(charcode);

        FX_WORD unicode = 0;
        if (swUnicode.GetLength() > 0)
            unicode = swUnicode[0];

        if (ISLATINWORD(unicode) && bIsLatin)
        {
        }
        else
        {
            bIsLatin = ISLATINWORD(unicode);
            if (unicode != 0x20)
                nWords++;
        }

        if (nWords-1 == nWordIndex)
            swRet += unicode;
    }

    return swRet;
}

FX_BOOL Document::zoom(IFXJS_Context* cc, CJS_PropValue& vp, CFX_WideString& sError)
{

    return TRUE;
}

/**
(none,  NoVary)
(fitP,  FitPage)
(fitW,  FitWidth)
(fitH,  FitHeight)
(fitV,  FitVisibleWidth)
(pref,  Preferred)
(refW,  ReflowWidth)
*/

FX_BOOL Document::zoomType(IFXJS_Context* cc, CJS_PropValue& vp, CFX_WideString& sError)
{
    return TRUE;
}

FX_BOOL Document::deletePages(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
  // Unsafe, no supported.
  return TRUE;
}

FX_BOOL Document::extractPages(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
  // Unsafe, not supported.
  return TRUE;
}

FX_BOOL Document::insertPages(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
  // Unsafe, not supported.
  return TRUE;
}

FX_BOOL Document::replacePages(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
  // Unsafe, not supported.
  return TRUE;
}

FX_BOOL Document::getURL(IFXJS_Context* cc, const CJS_Parameters& params, CJS_Value& vRet, CFX_WideString& sError)
{
  // Unsafe, not supported.
  return TRUE;
}

void Document::AddDelayData(CJS_DelayData* pData)
{
    m_DelayData.Add(pData);
}

void Document::DoFieldDelay(const CFX_WideString& sFieldName, int nControlIndex)
{
    CFX_DWordArray DelArray;
    CFX_ArrayTemplate<CJS_DelayData*> DelayDataForFieldAndControlIndex;

    for (int i=0,sz=m_DelayData.GetSize(); i<sz; i++)
    {
        if (CJS_DelayData* pData = m_DelayData.GetAt(i))
        {
            if (pData->sFieldName == sFieldName && pData->nControlIndex == nControlIndex)
            {
                DelayDataForFieldAndControlIndex.Add(pData);
                m_DelayData.SetAt(i, NULL);
                DelArray.Add(i);
            }
        }
    }

    for (int j=DelArray.GetSize()-1; j>=0; j--)
    {
        m_DelayData.RemoveAt(DelArray[j]);
    }

    for (int i=0,sz=DelayDataForFieldAndControlIndex.GetSize(); i < sz; i++)
    {
        CJS_DelayData* pData = DelayDataForFieldAndControlIndex.GetAt(i);
        Field::DoDelay(m_pDocument, pData);
        DelayDataForFieldAndControlIndex.SetAt(i,NULL);
        delete pData;
    }
}

void Document::AddDelayAnnotData(CJS_AnnotObj *pData)
{
    m_DelayAnnotData.Add(pData);
}

void Document::DoAnnotDelay()
{
    CFX_DWordArray DelArray;

    for (int j=DelArray.GetSize()-1; j>=0; j--)
    {
        m_DelayData.RemoveAt(DelArray[j]);
    }
}

CJS_Document* Document::GetCJSDoc() const
{
    return static_cast<CJS_Document*>(m_pJSObject);
}
